package com.lagou.filter;/**
 * Created by IntelliJ IDEA.
 * User: Yangdx.
 * Date: 2020/7/25.
 * Time: 3:10.
 * Tags: Code, we are serious.
 */

import com.lagou.util.HttpRequestUtil;
import com.lagou.util.HttpResponseStatusUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author
 * @create 2020-07-25 3:10
 **/
@Component
public class IPRequestFilter extends HttpResponseStatusUtil implements GlobalFilter, Ordered {
    @Value("${config.request.iplimit.apis}")
    private String[] restrictApis;

    @Value("${config.request.iplimit.intervalMinutes}")
    private int requestIntervalMinutes;

    @Value("${config.request.iplimit.maxRequestCount}")
    private int maxRequestCount;

    @Value("${config.response.message.seeother}")
    private String seeOtherStatusText;

    private final Map<String, ClientIntervalRequestCounter> clientRequestMap = new HashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        for (String restrictApi : restrictApis) {
            if (path.toLowerCase().startsWith(restrictApi)) {
                return doFilter(exchange, chain);
            }
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String clientIp = HttpRequestUtil.getRequestClientIP(exchange.getRequest());

        ClientIntervalRequestCounter counter;
        if (clientRequestMap.containsKey(clientIp)) {
            counter = clientRequestMap.get(clientIp);
        } else {
            counter = new ClientIntervalRequestCounter(this.requestIntervalMinutes * 60, clientIp);
            clientRequestMap.put(clientIp, counter);
        }

        if (counter.getTotalCount() >= maxRequestCount) {
            return responseStatusAndMessage(exchange.getResponse(), HttpStatus.SEE_OTHER, seeOtherStatusText);
        }

        return chain.filter(exchange);
    }

    static class ClientIntervalRequestCounter {

        static class CounterItem {

            private final int generation;
            private int count;

            public CounterItem(int generation) {
                this.generation = generation;
                this.count = 0;
            }

            public void increase(int generation) {
                if (generation == this.generation) {
                    this.count++;
                } else {
                    this.count = 1;
                }
            }

            public int getCount() {
                return count;
            }

            public int getGeneration() {
                return generation;
            }
        }

        private int intervalKeyCount;
        private String clientIP;

        private int generation;
        private long startTimePoint;
        private long lastTimePoint;
        private CounterItem[] counterItems;

        public ClientIntervalRequestCounter(int intervalKeyCount, String clientIP) {
            this.intervalKeyCount = intervalKeyCount;
            this.clientIP = clientIP;

            this.init(getTimePoint());
        }

        private long getTimePoint() {
            return new Date().getTime() / 1000;
        }

        private void init(long timePoint) {
            this.generation = 0;
            this.startTimePoint = timePoint;
            this.lastTimePoint = timePoint;
            this.counterItems = new CounterItem[intervalKeyCount];
        }

        private synchronized boolean expired() {
            long nowTimePoint = getTimePoint();
            if (startTimePoint > nowTimePoint) {
                return true;
            }

            if (nowTimePoint - lastTimePoint > intervalKeyCount) {
                this.init(nowTimePoint);
                return true;
            }

            this.lastTimePoint = nowTimePoint;
            return false;
        }

        private synchronized void increase() {
            if (this.expired()) return;

            long nowTimePoint = new Date().getTime() / 1000;
            this.generation = (int) ((nowTimePoint - startTimePoint) / intervalKeyCount);

            int key = (int) ((nowTimePoint - startTimePoint) % intervalKeyCount);
            for (int i = 0; i < key; i++) {
                CounterItem counterItem = this.counterItems[i];
                if (counterItem != null && counterItem.getGeneration() < this.generation) {
                    this.counterItems[i] = new CounterItem(this.generation);
                }
            }

            if (this.counterItems[key] == null) {
                this.counterItems[key] = new CounterItem(this.generation);
            }
            this.counterItems[key].increase(this.generation);
        }

        private synchronized int count() {
            int totalCount = 0;
            for (CounterItem counterItem : this.counterItems) {
                if (counterItem != null) {
                    totalCount += counterItem.getCount();
                }
            }
            return totalCount;
        }

        public int getTotalCount() {
            this.increase();
            return this.count();
        }
    }
}
